﻿using SuperSocket.ClientEngine;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net;
using System.Threading;
using ErrorEventArgs = SuperSocket.ClientEngine.ErrorEventArgs;
using System.Threading.Tasks;
using SuperX.SuperSocket.Socket.Transfer.Server;
using SuperX.SuperSocket.Socket.Transfer.Extend;
using SuperX.SuperSocket.Socket.Transfer.Client.UpLoad.Command;

namespace SuperX.SuperSocket.Socket.Transfer.Client.UpLoad
{
    internal class UpLoadClientEngine : IDisposable
    {
        internal FileUploadingEvents FileUploadingEvents;

        protected IClientCommandReader<TransferCommandInfo> CommandReader { get; private set; }

        private Dictionary<string, ICommand<UpLoadClientEngine, TransferCommandInfo>> m_CommandDict
            = new Dictionary<string, ICommand<UpLoadClientEngine, TransferCommandInfo>>(StringComparer.OrdinalIgnoreCase);

        private string _fileName;
        private string _saveName;
        internal FileInfo UpLoadInfo;
        internal FileStream m_fileStream;
        internal int PacketSize = 1024 * 30;
        internal byte[] readBuffer;
        internal int StatusCode = 0;
        internal TcpClientSession Client;
        internal AutoResetEvent m_OpenedEvent = new AutoResetEvent(false);
        internal AutoResetEvent m_EndEvent = new AutoResetEvent(false);
        internal AutoResetEvent m_StopEvent = new AutoResetEvent(false);
        internal object lockobj = new object();
        private EndPoint _endPoint;

        private Timer _timeoutTimer;
        public int TimeoutCount { get; set; } = 1000 * 30; //毫秒

        public UpLoadClientEngine(FileUploadingEvents _FileUploadingEvents)
        {
            FileUploadingEvents = _FileUploadingEvents;
        }

        public void Init(EndPoint endpoint, string fileName, string saveName)
        {
            string str = ConfigurationManager.AppSettings["PacketSize"];
            if (!string.IsNullOrWhiteSpace(str))
            {
                PacketSize = Convert.ToInt32(str);
            }
            readBuffer = new byte[PacketSize];
            _fileName = fileName;
            _saveName = saveName;
            UpLoadCommandBase[] baseArray = new UpLoadCommandBase[] {
                new DoUpLoad(),
                new DoExists(),
                new DoCheckServerComplete(),
                new DoStop() };
            foreach (UpLoadCommandBase base2 in baseArray)
            {
                m_CommandDict.Add(base2.Name, base2);
            }
            _endPoint = endpoint;
            TcpClientSession session = new AsyncTcpSession();
            session.ReceiveBufferSize = 1024 * 3;
            session.Error += new EventHandler<ErrorEventArgs>(client_Error);
            session.DataReceived += new EventHandler<DataEventArgs>(client_DataReceived);
            session.Connected += new EventHandler(client_Connected);
            session.Closed += new EventHandler(client_Closed);
            Client = session;
            CommandReader = new ClientLoadReader();
        }

        public void StartUpLoad(ref string fileProjectId)
        {
            try
            {
                Client.Connect(_endPoint);
                if (!m_OpenedEvent.WaitOne(5000))
                {
                    throw new Exception("连接失败");
                }
                m_fileStream = new FileStream(_fileName, FileMode.Open, FileAccess.Read);
                FileInfo configInfo = null;
                string configName = _fileName + Config.ConfigSuffix;
                if (File.Exists(configName))
                {
                    File.Delete(configName);
                    //try
                    //{
                    //    configInfo = SerializeHelp.DeserializeFromFile<FileInfo>(configName);
                    //    //验证配置文件正确性
                    //    //TODO:
                    //}
                    //catch (Exception ex)
                    //{
                    //    configInfo = null;
                    //    ex.ToString();
                    //    //logxxx
                    //    File.Delete(configName);
                    //}
                }
                if (configInfo == null)
                {
                    configInfo = new FileInfo();
                    configInfo.FileName = _fileName;
                    configInfo.FileSize = m_fileStream.Length;
                    configInfo.ProjectId = Guid.NewGuid().ToString();
                    configInfo.SaveName = _saveName;
                    configInfo.TransferedLength = 0;
                    configInfo.TransferLength = m_fileStream.Length;
                    configInfo.TransferPos = 0;
                    SerializeHelp.SerializeToFile<FileInfo>(configInfo, configName);
                }
                UpLoadInfo = configInfo;
                fileProjectId = UpLoadInfo.ProjectId;
                byte[] data = SerializeHelp.Serialize<FileInfo>(UpLoadInfo);
                SendData(UpLoadOP.DoUpLoad, data, 0, data.Length);
                _timeoutTimer = new Timer(TimerCallback, null, TimeoutCount, Timeout.Infinite);
            }
            catch (Exception exception)
            {
                OnTransferError(new TransferErrorEventArgs(exception));
            }
        }

        private void TimerCallback(object state)
        {
            var message = string.Format("上传文件超时{0}毫秒 文件名：{1}", TimeoutCount, _fileName);
            ConsoleLogHelper.Warn(message);
            OnTransferError(new TransferErrorEventArgs(new TimeoutException(message)));
        }

        private void ExecuteCommand(TransferCommandInfo commandInfo)
        {
            ICommand<UpLoadClientEngine, TransferCommandInfo> command;
            if (m_CommandDict.TryGetValue(commandInfo.Key, out command))
            {
                command.ExecuteCommand(this, commandInfo);
            }
        }

        private void m_OnDataReceived(byte[] data, int offset, int length)
        {
            while (true)
            {
                if (CommandReader == null)
                    return;
                int rest;
                TransferCommandInfo commandInfo = CommandReader.GetCommandInfo(data, offset, length, out rest);
                if (CommandReader.NextCommandReader != null)
                {
                    CommandReader = CommandReader.NextCommandReader;
                }
                if (commandInfo != null)
                {
                    ExecuteCommand(commandInfo);
                }
                if (rest <= 0)
                {
                    return;
                }
                offset = offset + length - rest;
                length = rest;
            }
        }

        private void client_Closed(object sender, EventArgs e)
        {
            m_EndEvent.Set();
            //OnTransferError(new TransferErrorEventArgs(new TransferSessionAbortException("会话异常关闭")));
        }

        private void client_Connected(object sender, EventArgs e)
        {
            m_OpenedEvent.Set();
        }

        private void client_DataReceived(object sender, DataEventArgs e)
        {
            m_OnDataReceived(e.Data, e.Offset, e.Length);
        }

        private void client_Error(object sender, ErrorEventArgs e)
        {
            OnTransferError(new TransferErrorEventArgs(e.Exception));
        }

        public void Dispose()
        {
            _timeoutTimer?.Dispose();
            _timeoutTimer = null;
            CommandReader = null;
            m_CommandDict?.Clear();
            _fileName = string.Empty;
            _saveName = string.Empty;
            UpLoadInfo = null;
            if (m_fileStream != null)
            {
                m_fileStream.Close();
                m_fileStream = null;
            }
            if (FileUploadingEvents != null && FileUploadingEvents.OnFileClose != null)
                FileUploadingEvents.OnFileClose(new EventArgs());
            if (FileUploadingEvents != null)
                FileUploadingEvents = null;
            TcpClientSession client = Client;
            if (client != null)
            {
                client.Error -= new EventHandler<ErrorEventArgs>(client_Error);
                client.DataReceived -= new EventHandler<DataEventArgs>(client_DataReceived);
                client.Connected -= new EventHandler(client_Connected);
                client.Closed -= new EventHandler(client_Closed);
                if (client.IsConnected)
                {
                    client.Close();
                }
                Client = null;
            }
            readBuffer = null;
            if (m_OpenedEvent != null)
            {
                m_OpenedEvent.Reset();
                m_OpenedEvent = null;
            }
            if (m_EndEvent != null)
            {
                m_EndEvent.Reset();
                m_EndEvent = null;
            }
        }

        internal void OnTransferStart()
        {
            _timeoutTimer?.Change(TimeoutCount, Timeout.Infinite);
            if (FileUploadingEvents.OnTransferStart != null)
            {
                FileUploadingEvents.OnTransferStart(new TransferStartEventArgs(UpLoadInfo.ProjectId));
            }
        }

        internal bool OnFileExist()
        {
            var result = false;
            if (FileUploadingEvents.OnFileExist != null)
            {
                result = FileUploadingEvents.OnFileExist(new EventArgs());
            }
            else
                OnTransferError(new TransferErrorEventArgs(new Exception("存在相同文件")));
            return result;
        }

        internal void OnExistUploading(ServerUploadStateInfo info)
        {
            if (FileUploadingEvents.OnTransferExistUploading != null)
            {
                FileUploadingEvents.OnTransferExistUploading(new TransferFileExistUplodingEventArgs(info));
                Dispose();
            }
            else
            {
                OnTransferError(new TransferErrorEventArgs(new Exception($"存在有另一客户端{info.RemoteIP}正在往服务器上传文件")));
            }
        }

        internal void OnTransferError(TransferErrorEventArgs e)
        {
            if (FileUploadingEvents != null && FileUploadingEvents.OnTransferError != null)
            {
                FileUploadingEvents.OnTransferError(e);
            }
            Dispose();
        }

        internal void OnTransferStep(long _TotalLen, long _TransferLen, int _CurrentPacket)
        {
            _timeoutTimer?.Change(TimeoutCount, Timeout.Infinite);
            if (FileUploadingEvents.OnTransferStep != null)
            {
                FileUploadingEvents.OnTransferStep(new TransferStepEventArgs(_TotalLen, _TransferLen, _CurrentPacket));
            }
        }

        internal void SendData(UpLoadOP opCode, byte[] data, int offset, int length)
        {
            if (Client == null)
                return;
            byte[] senddata = new byte[length + 5];
            senddata[0] = 0;
            senddata[1] = (byte)((int)opCode / 256);
            senddata[2] = (byte)((int)opCode % 256);
            senddata[3] = (byte)(length / 256);
            senddata[4] = (byte)(length % 256);
            Buffer.BlockCopy(data, offset, senddata, 5, length);
            Client.Send(senddata, 0, senddata.Length);
        }

        internal void Pause()
        {
        }

        internal void Continue()
        {
        }

        internal void DoEnd()
        {
            if (StatusCode != 1 && StatusCode != 2)
            {
                string configName = _fileName + Config.ConfigSuffix;
                if (File.Exists(configName))
                {
                    File.Delete(configName);
                }
            }
            if (m_fileStream != null)
            {
                m_fileStream.Close();
                m_fileStream = null;
            }
            if (FileUploadingEvents?.OnTransferComplete != null)
            {
                FileUploadingEvents.OnTransferComplete(new EventArgs());
            }
            Dispose();
        }

        internal void StopUpLoad()
        {
            try
            {
                StatusCode = 1;
                //new Action(() =>
                //{
                //    this.m_StopEvent.WaitOne();
                //    this.SendData(UpLoadOP.DoStop, new byte[] { 1 }, 0, 1);
                //    this.m_StopEvent.WaitOne(5000);
                //    lock (this.lockobj)
                //    {
                //        string configName = _fileName + Config.ConfigSuffix;
                //        SerializeHelp.SerializeToFile<UpLoadInfo>(this.UpLoadInfo, configName);
                //        this.DoEnd();
                //    }
                //}).BeginInvoke(null, null);

                Task.Factory.StartNew(() =>
                {
                    m_StopEvent.WaitOne();
                    SendData(UpLoadOP.DoStop, new byte[] { 1 }, 0, 1);
                    m_StopEvent.WaitOne(5000);
                    lock (lockobj)
                    {
                        string configName = _fileName + Config.ConfigSuffix;
                        SerializeHelp.SerializeToFile<FileInfo>(UpLoadInfo, configName);
                        DoEnd();
                    }
                });
            }
            catch (Exception ex)
            {
                OnTransferError(new TransferErrorEventArgs(ex));
            }
        }
    }
}